unit LogFile;

interface

uses
  SysUtils, Windows, Classes, SyncObjs, ServerAPI;

type
  ILogFile = interface(IInterface)
  ['{C3A2436A-3BC3-4E4D-9098-E93CD6DACB21}']
    procedure LogMessage(const AMessage: string); overload;
    procedure LogMessage(const AFmt: string; Args: array of const); overload;
    procedure load(const AUID: string);
    procedure save(const AUID: string);
  end;

  TLogFile = class(TInterfacedObject, ILogFile)
  private
    FAccessLock: TCriticalSection;
    FBufferList: TStringList;
    FLogFileName: string;
    FLogWriterThread: TThread;
    FLogEvent: TEvent;
    FExitEvent: TEvent;
    FAPI: TServerApi;
  private
    procedure DoFlush;
    procedure load(const AUID: string);
    procedure save(const AUID: string);
  protected
    { ILogFile }
    procedure LogMessage(const AMessage: string); overload;
    procedure LogMessage(const AFmt: string; Args: array of const); overload;
  protected
    procedure Send(const AMsg: string); inline;

    property  LogFileName: string
      read FLogFileName write FLogFileName;
  public
    constructor Create(const ALogFileName: string);
    destructor Destroy; override;
  end;

  function Logger: ILogFile;

implementation



const
  defLogBufferCount     = 1000;
  defLogEventTimeOut    = 10 * 1000;

type
  TSysLogWriterThread = class(TThread)
  private
    FSysLog: TLogFile;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean; ASysLog: TLogFile);
    destructor Destroy; override;
  end;

var
  GlobalLogger: ILogFile;
  GlobalCriticalSection: TCriticalSection;


function Logger: ILogFile;
begin
  if GlobalLogger = nil then
  begin
    GlobalCriticalSection.Enter;
    try
      if GlobalLogger = nil then
        GlobalLogger := TLogFile.Create('');
    finally
      GlobalCriticalSection.Leave;
    end;
  end;
  Result := GlobalLogger;
end;

{ TLogFile }

constructor TLogFile.Create(const ALogFileName: string);
begin
  inherited Create;
  FAPI := TServerApi.Create;
  FLogFileName := ALogFileName;
  FAccessLock := TCriticalSection.Create;
  FBufferList := TStringList.Create;
  FLogWriterThread := TSysLogWriterThread.Create(True, Self);
  FLogEvent := TEvent.Create(nil, False,False, '');
  FExitEvent := TEvent.Create(nil, False, False, '');
end;


destructor TLogFile.Destroy;
begin
  if FBufferList.Count > 0 then
    DoFlush;
  FreeAndNil(FAPI);
  FLogWriterThread.Terminate;
  FExitEvent.SetEvent;
  if not FLogWriterThread.Suspended then
    FLogWriterThread.WaitFor;
  FreeAndNil(FLogWriterThread);
  FreeAndNil(FAccessLock);
  FreeAndNil(FBufferList);
  FreeAndNil(FLogEvent);
  FreeAndNil(FExitEvent);
  inherited;
end;


procedure TLogFile.DoFlush;
var
  stream: TFileStream;
  S: string;
  arrBom: TBytes;
begin
  if FBufferList.Count = 0 then
    Exit;
  if FLogFileName = '' then
    FLogFileName := ChangeFileExt(GetModuleName(0), '.log');
  if FileExists(FLogFileName) then
    stream := TFileStream.Create(FLogFileName, fmOpenReadWrite or fmShareDenyWrite)
  else begin
    stream := TFileStream.Create(FLogFileName, fmCreate or fmShareDenyWrite);
    SetLength(arrBom, 0);
{$IFDEF Unicode}
    arrBom := TEncoding.Unicode.GetPreamble;
{$ENDIF}
    stream.WriteBuffer(arrBom[0], Length(arrBom));
  end;
  try
    stream.Seek(0, soFromEnd);
    while FBufferList.Count > 0 do
    begin
      FAccessLock.Enter;
      try
        S := FBufferList[0] + #13#10;
        FBufferList.Delete(0);
      finally
        FAccessLock.Leave;
      end;
      stream.Write(PChar(S)^, Length(S) * SizeOf(Char));
    end;
  finally
    stream.Free;
  end;
end;

procedure TLogFile.load(const AUID: string);
begin
  FBufferList.Text := FAPI.GetLog(AUID);
end;

procedure TLogFile.LogMessage(const AMessage: string);
begin
  Send(AMessage);
end;

procedure TLogFile.LogMessage(const AFmt: string; Args: array of const);
begin
  Send(Format(AFmt, Args));
end;


procedure TLogFile.save(const AUID: string);
begin
  FAPI.UpdateLog(AUID, FBufferList.Text)
end;

procedure TLogFile.Send(const AMsg: string);
begin
  FAccessLock.Enter;
  try
    if FBufferList.Count > defLogBufferCount then
      Exit;
    FBufferList.Add(AMsg);
    if FLogWriterThread.Suspended then
      FLogWriterThread.Suspended := False;
    FLogEvent.SetEvent;
  finally
    FAccessLock.Leave;
  end;
end;

{ TSysLogWriterThread }

constructor TSysLogWriterThread.Create(CreateSuspended: Boolean;
  ASysLog: TLogFile);
begin
  Assert(ASysLog <> nil);
  FSysLog := ASysLog;
  inherited Create(CreateSuspended);
end;

destructor TSysLogWriterThread.Destroy;
begin

  inherited;
end;


procedure TSysLogWriterThread.Execute;
var
  handles: array [0..1] of THandle;
begin
  handles[0] := FSysLog.FLogEvent.Handle;
  handles[1] := FSysLog.FExitEvent.Handle;
  while not Terminated do
  begin
    case WaitForMultipleObjects(2, @handles, False, defLogEventTimeOut) of
      WAIT_TIMEOUT:
        ;
      WAIT_OBJECT_0:
        FSysLog.DoFlush;
      WAIT_OBJECT_0 + 1:
        Exit;
    else
      Terminate;
    end;
  end;
end;

initialization
  GlobalLogger := nil;
  GlobalCriticalSection := TCriticalSection.Create;

finalization
  GlobalLogger := nil;
  GlobalCriticalSection.Free;

end.
